package org.moonshiner.controller.service.impl;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletResponse;

import org.moonshiner.controller.dao.hibernate.PgProjectDaoImpl;
import org.moonshiner.controller.properties.PropertiesProvider;
import org.moonshiner.controller.security.EncryptorFactory;
import org.moonshiner.controller.security.FileEncryptor;
import org.moonshiner.controller.service.ProjectService;
import org.moonshiner.model.constants.PropertiesKey;
import org.moonshiner.model.project.Project;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProjectServiceImpl extends
		GenericService<PgProjectDaoImpl, Project> implements ProjectService {

	@Autowired
	public ProjectServiceImpl(PgProjectDaoImpl dao) {
		super(dao);
	}

	@Override
	@Transactional
	public void addFileToProject(Project project, String fileName,
			String sourceCode, String password) {

		updateProject(project);

		saveFile(project, fileName, sourceCode, password);

	}

	@Override
	@Transactional
	public List<File> getFilesList(Project project, String path) {

		updateProject(project);

		File targetFolder = getTargetFolder(project, path);

		List<File> files = new ArrayList<File>();
		if (targetFolder.exists()) {
			files = Arrays.asList(targetFolder.listFiles());
		}

		return files;
	}

	@Override
	@Transactional
	public void writeFileToResponse(HttpServletResponse response,
			Project project, String path) {
		updateProject(project);

		File targetFile = getTargetFolder(project, path);
		if (!targetFile.isDirectory()) {
			writeFile(response, project, targetFile);
		}

	}

	@Override
	public void test() {
		System.out.println("test");
	}

	private void writeFile(HttpServletResponse response, Project project,
			File targetFile) {

		FileInputStream fis = null;
		OutputStream fos = null;
		try {
			fis = new FileInputStream(targetFile);
			fos = response.getOutputStream();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (project.getEncryptionNeeded()) {
			FileEncryptor fileEncryptor = new EncryptorFactory()
					.getFileEncryptor(project.getAlgorithm());
			fileEncryptor.doCrypt(project.getPassword(), fis, fos,
					Cipher.DECRYPT_MODE);
		} else {
			doCopy(fis, fos);
		}
	}

	private File getTargetFolder(Project project, String path) {
		String rootFolderPath = buildPath(
				PropertiesProvider.getProperty(PropertiesKey.PROJECTS_FOLDER),
				"/", project.getUser().getName(), "/", "projects/",
				project.getName(), "/");

		File rootFolder = new File(rootFolderPath);

		File targetFolder = new File(buildPath(rootFolderPath, path));

		try {
			rootFolderPath = rootFolder.getCanonicalPath();
			String folderPath = targetFolder.getCanonicalPath();

			if (folderPath.compareTo(rootFolderPath) < 0) {
				targetFolder = rootFolder;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return targetFolder;
	}

	private void saveFile(Project project, String fileName, String sourceCode,
			String password) {

		String folderPath = buildPath(
				PropertiesProvider.getProperty(PropertiesKey.PROJECTS_FOLDER),
				"/", project.getUser().getName(), "/", "projects/",
				project.getName());

		File folder = new File(folderPath);
		folder.mkdirs();

		if (project.getEncryptionNeeded()) {
			encryptFile(project, fileName, sourceCode, password, folderPath);
		} else {
			writeToFile(fileName, sourceCode, folderPath);
		}

	}

	private void encryptFile(Project project, String fileName,
			String sourceCode, String password, String folderPath) {
		String algorithm = project.getAlgorithm().toString();

		FileEncryptor fileEncryptor = new EncryptorFactory()
				.getFileEncryptor(algorithm);

		String encryptedFileName = buildPath(folderPath, "/", fileName,
				".java.enc");

		FileOutputStream fos = null;
		InputStream is = new ByteArrayInputStream(sourceCode.getBytes());
		try {
			fos = new FileOutputStream(encryptedFileName);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		fileEncryptor.doCrypt(password, is, fos, Cipher.ENCRYPT_MODE);
	}

	private void writeToFile(String fileName, String sourceCode,
			String folderPath) {
		File file = new File(buildPath(folderPath, "/", fileName, ".java"));
		BufferedWriter bfw = null;
		try {
			FileWriter writer = new FileWriter(file.getAbsoluteFile());
			bfw = new BufferedWriter(writer);
			bfw.write(sourceCode);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				bfw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void updateProject(Project project) {
		getDao().getSessionFactory().getCurrentSession().update(project);
	}

	private String buildPath(String... args) {
		StringBuilder builder = new StringBuilder();

		for (String string : args) {
			builder.append(string);
		}

		return builder.toString();
	}

	private void doCopy(InputStream in, OutputStream out) {
		byte[] buffer = new byte[128];
		int length = 0;
		try {
			while ((length = in.read(buffer)) >= 0) {
				out.write(buffer, 0, length);
			}
		} catch (IOException exception) {

		} finally {
			try {
				out.flush();
				out.close();
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}
